#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
import re
import time
from pathlib import Path

from common.common import check_path_legal, execute_cmd
from common.const import ParamConstant, SubJobStatusEnum, CMDResult
from common.common_models import SubJobDetails
from common.enums.common_enums import DeployTypeEnum
from common.security.anonym_utils.anonymity import Anonymity
from generaldb.saphana.comm.common_util import get_localhost_hostname
from generaldb.saphana.comm.saphana_const import SaphanaProgressPhase, SaphanaReportInterval, \
    SaphanaJsonConstant, SaphanaReportStatus, SaphanaErrorCode
from generaldb.saphana.comm.saphana_file_operate import log, FileOperate
from saphana.restore.saphana_restore_parent import SaphanaRestoreBackintParent
from saphana.restore.saphana_parse_restore_params import SaphanaParseRestoreParam


class SaphanaRestoreSystemDb(SaphanaRestoreBackintParent):
    def __init__(self, pid, job_id, subjob_id, parse_params_obj: SaphanaParseRestoreParam):
        super(SaphanaRestoreSystemDb, self).__init__(pid, job_id, subjob_id, parse_params_obj)
        self._error_code = 0
        self._error_param_list = []

    def wait_restore(self):
        # 系统数据库恢复 等待用户用其他工具恢复完成
        file_name = os.path.join(ParamConstant.PARAM_FILE_PATH, f"hana_systemdb_{self._job_id}")
        if not check_path_legal(file_name, ParamConstant.PARAM_FILE_PATH):
            log.error(f"Path({file_name}) not legal. {self._parse_param.get_log_common()}")
            return False
        while True:
            if os.path.exists(file_name):
                break
            time.sleep(SaphanaReportInterval.REPORT_PROGRESS_INTERVAL)
        output = FileOperate.read_json_file(file_name)
        if not output:
            log.error(f"Output is null. {self._parse_param.get_log_common()}")
            return False
        status = output.get(SaphanaJsonConstant.STATUS, "FAIL")
        if status != SaphanaReportStatus.SUCCESS:
            log.error(f"Recv status({status}) failed. {self._parse_param.get_log_common()}")
            return False
        log.info(f"Restore success. {self._parse_param.get_log_common()}")
        return True

    def progress_func(self):
        sub_job_details = SubJobDetails(taskId=self._job_id, subTaskId=self._subjob_id,
                                        progress=SaphanaProgressPhase.RUNNING,
                                        taskStatus=SubJobStatusEnum.RUNNING.value).dict(by_alias=True)
        return sub_job_details

    def analyze_restore_output(self, output):
        """
        分析恢复命令的输入回显，如果没有错误，返回True，反之返回False
        """
        error_msg_list = ["recoverSys failed", "error", "ERROR"]
        has_error = True if any(item in output for item in error_msg_list) else False
        # 如果没有明确恢复成功的，恢复失败
        result_msg = None
        for single_line in output.split('\n'):
            if "ending recoverSys" in single_line:
                result_msg = single_line
                break
        result_msg = result_msg[result_msg.find("rc"):-1]
        result_msg = result_msg[result_msg.find('=') + 1:result_msg.find('(')].strip()
        if has_error or result_msg != "0":
            return False
        return True

    def exec_restore_data_cmd(self):
        tmp_dir = f"DB_{self._db_name.upper()}"
        instance_dir = self._saphana_cluster.get_dir_instance()
        if self._saphana_cluster.is_multi_system():
            tmp_dir = self._db_name.upper()
        restore_cmd = f"su - {self._sid}adm -c \"HDBSettings.sh recoverSys.py --command=" \
            f"\\\"RECOVER DATA using " \
            f"backint('{instance_dir}/SYS/global/hdb/backint/{tmp_dir}/') " \
            f"clear log\\\" --wait\""
        log.info(f"Begin to execute restore cmd: {restore_cmd}")
        ret, output, err = execute_cmd(restore_cmd)
        # 如果输出中有错误日志，恢复失败
        is_success = self.analyze_restore_output(output)
        if ret != CMDResult.SUCCESS or not is_success:
            log.error(f"Exec restore data cmd failed. out:{output}. {self._parse_param.get_log_common()}")
            self._error_code = SaphanaErrorCode.EXEC_BACKUP_RECOVER_CMD_FAIL
            self._error_param_list = ["recover", Anonymity.process(output)] if output else ["recover", "unknown"]
            return False
        return True

    def exec_restore_log_cmd(self, restore_time, cata_log_path, log_data_path, backup_id):
        if not self._check_recover(cata_log_path, cata_log_path, cata_log_path, restore_time):
            log.error(f"Check recover failed. {cata_log_path} {restore_time}")
            return False
        using_delta = self._parse_param.get_using_delta()
        log.info(f"using_delta: {using_delta}")
        if not using_delta:
            self._ignore_delta = True
        restore_cmd = f"su - {self._sid}adm -c \"HDBSettings.sh recoverSys.py --command=" \
                      f"\\\"RECOVER DATABASE UNTIL TIMESTAMP '{restore_time}' CLEAR LOG USING CATALOG PATH " \
                      f"('{cata_log_path}/') " \
                      f"USING BACKUP_ID {backup_id}" \
                      f"{' IGNORE DELTA DATA BACKUPS' *self._ignore_delta}\\\" --wait\""
        log.info(f"Begin to execute restore cmd: {restore_cmd}")
        ret, output, err = execute_cmd(restore_cmd)
        is_success = self.analyze_restore_output(output)
        if ret != CMDResult.SUCCESS or not is_success:
            log.error(f"Exec restore log cmd failed. out:{output}. {self._parse_param.get_log_common()}")
            self._error_code = SaphanaErrorCode.EXEC_BACKUP_RECOVER_CMD_FAIL
            self._error_param_list = ["recover", Anonymity.process(output)] if output else ["recover", "unknown"]
            return False
        return True

    def allow_restore(self):
        backup_obj_id = self._parse_param.get_backup_object_uuid()
        restore_obj_id = self._parse_param.get_restore_object_uuid()
        if backup_obj_id != restore_obj_id:
            # 如果恢复到新集群
            return True
        if self._parse_param.get_deploy_type() == DeployTypeEnum.SINGLE.value:
            log.info("Single deploy, allow restore.")
            return True
        # 集群 系统数据库 原位置恢复 命令，在主节点上执行
        local_hostname = get_localhost_hostname()
        master_hostname = self._get_master_hostname()
        if master_hostname and master_hostname.upper() != local_hostname.upper():
            log.error(f"Host : {local_hostname} is not the master host {master_hostname}, can not run.")
            return False
        return True

    def _get_master_hostname(self) -> str:
        path = Path(f"{self._saphana_cluster.get_dir_instance()}/SYS/global/hdb/custom/config/nameserver.ini")
        if not path.exists():
            log.warn(f"nameserver.ini not exist. {path}")
            return ""
        match = re.search(r"active_master\s+=\s+(\S+)", path.read_text("utf-8"))
        name = match.group(1).split(":")[0] if match else ""
        log.info(f"get master hostname: {name}")
        return name

